package terra.model.runnable;

import terra.Config;
import terra.managers.MapManager;
import terra.model.map.Map;
import terra.model.object.map.AbstractMapObject;
import terra.model.object.map.MapObject;
import terra.util.MathUtil;

public class WaterSimulater implements Runnable {
	@Override
	public void run() {
		try {
			while (true) {
				for (Map m : MapManager.getInstance().getMaps().values()) {
					if (!m.active)
						continue;
					simulateWaterPhysics(m, 1);
				}
				Thread.sleep(1000 / 60);
			} 
		} catch (InterruptedException e) { e.printStackTrace();	}
	}

	public static boolean simulateWaterPhysics(Map map, int times) {
		if (map.getWater().size() == 0)
			return false;
		for (int i = 0; i < times; i++) {
			float Flow = 0;
			// остаточная масса в ячейку
			float remaining_mass;
			// Calculate and apply flow for each block
			// Вычислить и применить поток каждому блоку
			for (int j = 0; j < map.getWater().size(); j++) {
				MapObject obj = map.getWater().get(j);
				Flow = 0;
				remaining_mass = obj.getMass();
				// System.out.println("0 mass remaining" +remaining_mass);
				// если остаточная масса <= 0, пропускаем
				if (remaining_mass <= 0)
					continue;
				AbstractMapObject down = obj.getNeighbor(MapObject.DOWN);
				if (down == null)
					down = obj.createNeighbor(MapObject.DOWN);
				// проверяем можно ли литься вниз
				if (down != null && down.getType() != MapObject.TYPE_GROUND) {
					Flow = getStableStateB(remaining_mass + down.getMass())
							- down.getMass();
					if (Flow > Config.MinFlow)
						Flow *= 0.75; // leads to smoother flow

					Flow = MathUtil.constrain(Flow, 0,
							Math.min(Config.MaxSpeed, remaining_mass));
					// System.out.println("flow 2 = " + Flow);
					obj.removeNewMass(Flow);
					((MapObject) down).addNewMass(Flow);
					remaining_mass -= Flow;
				}
				if (remaining_mass <= 0)
					continue;

				AbstractMapObject left = obj.getNeighbor(MapObject.LEFT);
				if (left == null)
					left = obj.createNeighbor(MapObject.LEFT);
				// Left
				if (left != null && left.getType() != MapObject.TYPE_GROUND) {
					// Equalize the amount of water in this block and it's
					// neighbour
					Flow = (obj.getMass() - left.getMass()) / 4;
					if (Flow > Config.MinFlow)
						Flow *= 0.75;

					Flow = MathUtil.constrain(Flow, 0, remaining_mass);
					obj.removeNewMass(Flow);
					((MapObject) left).addNewMass(Flow);
					remaining_mass -= Flow;
				}
				if (remaining_mass <= 0)
					continue;

				AbstractMapObject right = obj.getNeighbor(MapObject.RIGHT);
				if (right == null)
					right = obj.createNeighbor(MapObject.RIGHT);
				// Right
				if (right != null && right.getType() != MapObject.TYPE_GROUND) {
					// Equalize the amount of water in this block and it's
					// neighbour
					Flow = (obj.getMass() - right.getMass()) / 4;
					if (Flow > Config.MinFlow)
						Flow *= 0.75;

					Flow = MathUtil.constrain(Flow, 0, remaining_mass);
					obj.addNewMass(-Flow);
					((MapObject) right).addNewMass(Flow);
					remaining_mass -= Flow;
				}

				if (remaining_mass <= 0)
					continue;

				AbstractMapObject up = obj.getNeighbor(MapObject.UP);
				if (up == null)
					up = obj.createNeighbor(MapObject.UP);
				// Up. Only compressed water flows upwards.
				if (up != null && up.getType() != MapObject.TYPE_GROUND
						&& obj.getMass() >= Config.MaxMass) {
					// if (obj.getMass() < Config.MaxMass)
					// continue;
					Flow = remaining_mass
							- getStableStateB(remaining_mass + up.getMass());
					if (Flow > Config.MinFlow)
						Flow *= 0.5;

					Flow = MathUtil.constrain(Flow, 0,
							Math.min(Config.MaxSpeed, remaining_mass));
					obj.removeNewMass(Flow);
					((MapObject) up).addNewMass(Flow);
					remaining_mass -= Flow;
					// System.out.println("water up from " + obj.getX() + "_" +
					// obj.getY() + "_" + obj.getMass() + "    to " + up.getX()
					// + "_" + up.getY());
				}
			}
			for (int k = 0; k < map.getWater().size(); k++) {
				MapObject obj = map.getWater().get(k);
				obj.setMassFromNew();
				if (obj.getMass() < Config.MinMass)
					obj.nullMass();
			}

		}
		return true;
	}

	static float getStableStateB(float total_mass) {
		if (total_mass <= 1) {
			return 1;
		} else if (total_mass < 2 * Config.MaxMass + Config.MaxCompress) {
			return (Config.MaxMass * Config.MaxMass + total_mass
					* Config.MaxCompress)
					/ (Config.MaxMass + Config.MaxCompress);
		} else {
			return (total_mass + Config.MaxCompress) / 2;
		}
	}
}